
package cfar.ff.fun;

import cfar.ff.data.List;
import cfar.ff.interfaces.Functor;
import cfar.ff.interfaces.Pointed;
import cfar.ff.interfaces.Sequence;

import static cfar.ff.data.Assocs.*;
import static cfar.ff.fun.Seq.*;

import java.util.Iterator;

/**
 *
 * @author badi
 */
public class Fun {

    public static <A,B,C> F1<A,C> compose(final F1<B,C> bc, final F1<A,B> ab){
        return new F1<A, C>() {

            public C f(A a) {
                return bc.f(ab.f(a));
            }
        };
    }

    public static class zipWith<S,A,B,C> implements F3< F2<A,B,C>, Sequence<S,A>, Sequence<S,B>, Sequence<S,C> > {

        public Sequence<S, C> f(F2<A, B, C> f, Sequence<S, A> as, Sequence<S, B> bs) {
            int s = Math.min(as.length(), bs.length());
            Iterator<A> ai = as.iterator();
            Iterator<B> bi = bs.iterator();

            Sequence<S,C> res = (Sequence<S, C>) as.empty();
            while(ai.hasNext() && bi.hasNext()){
                A a = ai.next();
                B b = bi.next();
                res = snoc(f.f(a, b), res);
            }
            return res;
        }

    }

    public static <S,A,B,C> F3< F2<A,B,C>, Sequence<S,A>, Sequence<S,B>, Sequence<S,C> > zipWith(){
        return new zipWith<S,A,B,C>();
    }


    public static class zip<S,A,B> implements F2<Sequence<S,A>, Sequence<S,B>, Sequence<S,Assoc2<A,B>>> {

        public Sequence<S,Assoc2<A, B>> f(Sequence<S,A> as, Sequence<S,B> bs) {
            F2<A,B,Assoc2<A,B>> f = assoc2();
            F3<F2<A,B,Assoc2<A,B>>, Sequence<S,A>, Sequence<S,B>, Sequence<S,Assoc2<A,B>>> z = zipWith();
            return z.f(f, as, bs);
        }
    }

    public static <S,A,B> F2<Sequence<S,A>, Sequence<S,B>, Sequence<S,Assoc2<A,B>>> zip(){
        return new zip<S,A,B>();
    }


    public static class assoc2<A,B> implements F2<A,B,Assoc2<A,B>> {

        public Assoc2<A, B> f(A a, B b) {
            return new Assoc2<A, B>(a, b);
        }

    }

    public static <A,B> F2<A,B,Assoc2<A,B>> assoc2(){
        return new assoc2<A,B>();
    }



    public static class eq<A,B> implements F2<A,B,Boolean> {

        public Boolean f(A a, B b) {
            return a.equals(b);
        }
    }

    public static <A,B> F2<A,B,Boolean> eq(){
        return new eq<A,B>();
    }




    public static class foldl<S,A,B> implements F3<F2<A,B,A>, A, Sequence<S,B>, A>{

        public A f(F2<A, B, A> f, A a, Sequence<S,B> bs) {
            A _a = a;
            for(B b : bs)
                _a = f.f(_a, b);
            return _a;
        }
    }


    public static class foldl1<S,A> implements F2<F2<A,A,A>, Sequence<S,A>, A> {

        public A f(F2<A, A, A> f, Sequence<S, A> s) {
            A a1 = Seq.head(s);
            for(A a2 : Seq.tail(s))
                a1 = f.f(a1, a2);
            return a1;
        }
    }


    public static class and implements F1<Sequence<List, Boolean>, Boolean> {

        public Boolean f(Sequence<List, Boolean> a) {
            F2<F2, Sequence<List, Boolean>, Boolean> f = new foldl1();
            F2 eq = new F2<Boolean, Boolean, Boolean>() {

                public Boolean f(Boolean a, Boolean b) {
                    return a && b;
                }
            };
            return f.f(eq, a);
        }
    }

    public static final F1<Sequence<List, Boolean>, Boolean> and() {
        return new and();
    }

    public static <P,A,B> F2<Pointed<P,A>, B, Functor<P,B>> pure() {
        return new F2<Pointed<P, A>, B, Functor<P, B>>() {

            public Functor<P, B> f(Pointed<P, A> p, B b) {
                return p.<B>pure(b);
            }
        };
    }

}
